package com.beilang.base.util;

import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class DateUtil {
    private static Log log = LogFactory.getLog(DateUtil.class);
    public final static String DEFAULT_DATEMILLIS_FORMAT = "dd/MM/yyyy HH:mm:ss.SSS";
    public final static String DEFAULT_DATETIME_FORMAT = "dd/MM/yyyy HH:mm:ss";
    public final static String DATETIME_FORMAT_HHMM = "yyyy-MM-dd HH:mm";
    public final static String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public final static String PRINT_DATE_FORMAT = "yyyyMMdd";
    public final static String REPORT_DATE_FORMAT = "dd/MM/yyyy";    
    public final static String RFC3339_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
    private static DateFormat defaultDMF = new SimpleDateFormat(DEFAULT_DATEMILLIS_FORMAT);
    private static DateFormat defaultDTF = new SimpleDateFormat(DATETIME_FORMAT_HHMM);
    private static DateFormat defaultDF = new SimpleDateFormat(DEFAULT_DATE_FORMAT);
    private static DateFormat DATETIMEHHMM = new SimpleDateFormat(DATETIME_FORMAT_HHMM);
    private static DateFormat printDF = new SimpleDateFormat(PRINT_DATE_FORMAT);
    private static DateFormat reportDF = new SimpleDateFormat(REPORT_DATE_FORMAT);    
    private static String default_timezone = "Asia/China";

    /**
     * Returns the date instance based on the current system date and default timezone.
     * <p/>
     *
     * @return Current System date.
     */
    public static Date getDate() {
        TimeZone tz = TimeZone.getTimeZone(default_timezone);
        Calendar calendar = Calendar.getInstance(tz);
        return calendar.getTime();
    }

    /**
     * Returns the timestamp instance based on the current system date and default timezone.
     *
     * @return the current timestamp
     */
    public static Timestamp getCurrentTimestamp() {
        return new Timestamp(getDate().getTime());
    }

    /**
     * Format the input date to a date time string in the following format: <br>
     * <code>dd/MM/yyyy HH:mm:ss</code>
     *
     * @param date the date time value to be formatted into a date time string.
     * @return the formatted date time string;
     *         an empty String if the input date is <code>null</code> or
     *         if there is error during formatting.
     */
    public static String formatDateTime(Date date) {
        String dateTimeStr = "";

        try {
            dateTimeStr = defaultDTF.format(date);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }

        return dateTimeStr;
    }

    /**
     * Format the input date to a date time string in the following format: <br>
     * <code>dd/MM/yyyy HH:mm:ss.SSS</code>
     *
     * @param date the date time value to be formatted into a date time string.
     * @return the formatted date time string;
     *         an empty String if the input date is <code>null</code> or
     *         if there is error during formatting.
     */
    public static String formatDateMillis(Date date) {
        String dateTimeStr = "";

        try {
            dateTimeStr = defaultDMF.format(date);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }

        return dateTimeStr;
    }

    /**
     * Format the input date to a date time string in the following format: <br>
     * <code>dd/MM/yyyy</code>
     *
     * @param date the date time value to be formatted into a date time string.
     * @return the formatted date time string;
     *         an empty String if the input date is <code>null</code> or if there
     *         is an error during formatting
     */
    public static String formatDate(Date date) {
        String dateStr = "";

        try {
            dateStr = defaultDF.format(date);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }

        return dateStr;
    }

    /**
     * Parse the input date to a <code>Date</code> object.
     * The expected input date is of format <code>dd/MM/yyyy</code>
     *
     * @param dateStr the date string.
     * @return the date instance parsed from the date string;
     *         <code>null</code> if the date string is <code>null</code> or if the
     *         date string is invalid
     */
    public static Date parse(String dateStr) {
        Date date = null;

        try {
            date = defaultDF.parse(dateStr);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }

        return date;
    }

    public static Date parseReportDt(String dateStr) {
        Date date = null;

        try {
            date = reportDF.parse(dateStr);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }

        return date;
    }

    private static Date parseDate(String str, String[] parsePatterns) throws ParseException {
        if (str == null || parsePatterns == null) {
            throw new IllegalArgumentException("Date and Patterns must not be null");
        }

        SimpleDateFormat parser = null;
        ParsePosition pos = new ParsePosition(0);
        for (int i = 0; i < parsePatterns.length; i++) {
            if (i == 0) {
                parser = new SimpleDateFormat(parsePatterns[0]);
            } else {
                parser.applyPattern(parsePatterns[i]);
            }
            pos.setIndex(0);
            Date date = parser.parse(str, pos);
            if (date != null) {
                return date;
            }
        }
        throw new ParseException("Unable to parse the date: " + str, -1);
    }

    public static Date convertParse(String dateStr) {
        String[] parsePatterns = {DEFAULT_DATE_FORMAT, RFC3339_FORMAT};
        Date date;
        try {
            date = parseDate(dateStr, parsePatterns);
        } catch (ParseException e) {
            log.error("ERROR " + e);
            throw new RuntimeException(e);
        }
        return date;
    }

    public static Date getFullDate(Date date, Date time) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        Calendar timeCal = Calendar.getInstance();
        timeCal.setTime(time);

        cal.set(Calendar.HOUR_OF_DAY, timeCal.get(Calendar.HOUR_OF_DAY));
        cal.set(Calendar.MINUTE, timeCal.get(Calendar.MINUTE));
        cal.set(Calendar.SECOND, timeCal.get(Calendar.SECOND));

        return cal.getTime();
    }


    /**
     * Parse the input date to a <code>java.sql.Timestamp</code> object.
     * The expected input date is of format <code>dd/MM/yyyy</code> or
     * <code>dd/MM/yyyy HH:mm:ss</code>
     *
     * @param dateStr the date string.
     * @return the Timestamp instance created;
     *         <code>null</code> if the date string is <code>null</code>
     *         or it does not conform to the format <code>dd/MM/yyyy</code> or
     *         <code>dd/MM/yyyy HH:mm:ss</code>
     */
    public static java.sql.Timestamp parseTimestamp(String dateStr) {

        java.sql.Timestamp tsDate = null;

        if (dateStr != null && dateStr.length() == 10) {
            dateStr += " 00:00:00";
        } else if (dateStr != null && dateStr.length() == 16) {
            dateStr += ":00";
        }

        try {
            Date date = defaultDTF.parse(dateStr);
            tsDate = new java.sql.Timestamp(date.getTime());
        } catch (Exception e) {
            log.error("ERROR " + e);
        }

        return tsDate;
    }

    /**
     * Parse the input date to a <code>java.sql.Timestamp</code> object.
     * The expected input date is of format <code>dd/MM/yyyy HH:mm:ss.SSS</code>
     *
     * @param dateStr the date string.
     * @return the Timestamp instance created;
     *         <code>null</code> if the date string is <code>null</code>
     *         or it does not conform to the format <code>c HH:mm:ss.SSS</code>
     */
    public static java.sql.Timestamp parseDateMillis(String dateStr) {

        java.sql.Timestamp tsDate = null;

        try {
            if (dateStr != null && dateStr.length() > 23) dateStr = dateStr.substring(0, 23);
            Date date = defaultDMF.parse(dateStr);
            tsDate = new java.sql.Timestamp(date.getTime());
        } catch (Exception e) {
            // Do nothing
        }

        return tsDate;
    }

    public static java.sql.Timestamp convertParseDateMillis(String dateStr) {

        java.sql.Timestamp tsDate;

        try {
            if (dateStr != null && dateStr.length() > 23) dateStr = dateStr.substring(0, 23);
            Date date = defaultDMF.parse(dateStr);
            tsDate = new java.sql.Timestamp(date.getTime());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return tsDate;
    }

    /**
     * Format the input date to a date time string in the following format: <br>
     * <code>dd/MM/yyyy HH:mm:ss.ffffff</code>
     *
     * @param ts the timestamp value to be formatted into a timestamp string.
     * @return the formatted timestamp string;
     *         an empty String if the input date is <code>null</code> or
     *         if there is error during formatting.
     */
    public static String formatDateMicros(Timestamp ts) {
        String tsStr = "";

        try {
            tsStr = formatDateTime(ts);
            String micros = StringUtil.lPad(Integer.toString(ts.getNanos()), 9, "0");
            micros = micros.substring(0, 6);
            tsStr += "." + micros;
        } catch (Exception e) {
            // Do nothing
        }

        return tsStr;
    }

    /**
     * Tests the input value to ensure that a valid Date instance can be created from it.
     * Roll over dates are not allowed here and will return a false value.
     * Eg. isValidDate(2002, 10, 32) will return false.
     * <p/>
     *
     * @param year  The year value.
     * @param month The month value. ( 1 - 12 )
     * @param day   The day value. ( 1 - 31 )
     * @return True if all values can be used to create a single valid Date instance.
     */
    public static boolean isValidDate(int year, int month, int day) {

        if (day <= 0 || month <= 0 || year <= 0) return false;
        if (month > 12 || day > 31) return false;

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month - 1);

        // Find the maximum field value possible for the day with the year and month.
        int maxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH);

        return (day <= maxDay);
    }

    /**
     * Tests the input string to ensure that a valid Date instance can be created
     * according to the date format specified in the System property.
     * <p/>
     * If the properties file is not available or the dateformat property has
     * not been specified, the default format "dd/MM/yyyy"  will be used.
     * <p/>
     *
     * @param dateStr A date string.
     * @return True if it conforms to the system date format; False otherwise.
     */
    public static boolean isValidDate(String dateStr) {

        StringUtil.deNull(dateStr);
        if (dateStr.length() != 10) {
            return false;
        }

        boolean validDelimiter = (StringUtil.getCount(dateStr, '-') == 2 ||
                StringUtil.getCount(dateStr, '/') == 2 ||
                StringUtil.getCount(dateStr, '.') == 2);

        if (!validDelimiter) {
            return false;
        }

        String dd = dateStr.substring(0, 2);
        String mm = dateStr.substring(3, 5);
        String yyyy = dateStr.substring(6, 10);

        try {
            return isValidDate(Integer.parseInt(yyyy), Integer.parseInt(mm), Integer.parseInt(dd));
        } catch (Throwable t) {
            log.error("ERROR " + t);
            return false;
        }

    }

    /**
     * Tests if the inputs are valid time. When the ampm parameter is true,
     * the input hour will be tested for 12-hour format ( 1 ? 12 ).
     * When it is false, the input hour will be tested for 24-hour format ( 0 ? 23 ).
     * <p/>
     *
     * @param hour   The Hour value. ( 0 - 23  )
     * @param minute The Minute value. ( 0 - 59 )
     * @return True if the time inputs can be used to create a valid time instance.
     */
    public static boolean isValidTime(int hour, int minute) {
        return minute >= 0 && minute <= 59 && hour >= 0 && hour <= 23;
    }

    /**
     * get million seconds for input date
     *
     * @param date
     * @return million seconds
     */
    public static long getMillis(java.util.Date date) {
        java.util.Calendar c = java.util.Calendar.getInstance();
        c.setTime(date);
        return c.getTimeInMillis();
    }

    /**
     * Parse the input datetime string to a <code>Date</code> object.
     * The expected input date is of format <code>dd/MM/yyyy hh:mm:ss</code>
     *
     * @param dateTimeStr the date string.
     * @return the date instance parsed from the date string;
     *         <code>null</code> if the date string is <code>null</code> or if the
     *         date string is invalid
     */
    public static Date parseDateTime(String dateTimeStr) {
        Date date = null;
        try {
            date = defaultDTF.parse(dateTimeStr);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }
        return date;
    }

    /**
     * Date Arithmetic function. Adds the specified (signed) amount of time to
     * the given time field, based on the calendar's rules.
     * <p/>
     * For example, to subtract 5 days from a specific date, it can be achieved
     * by calling: <p>
     * DateUtil.add(date, Calendar.DATE, -5).
     * <p/>
     *
     * @param date   The date to perform the arithmetic function on
     * @param field  A Calendar constant to retrieve the field value from the Date
     * @param amount the amount of date or time to be added to the field
     * @return The date as a result of the execution of the arithmetic function.
     */
    public static Date add(Date date, int field, int amount) {
        TimeZone tz = TimeZone.getTimeZone(default_timezone);
        Calendar cal = Calendar.getInstance(tz);
        cal.setTime(date);
        cal.add(field, amount);

        return cal.getTime();
    }

    public static Date parseDateTimeHHMM(String dateTimeStr) {
        Date date = null;
        try {
            date = DATETIMEHHMM.parse(dateTimeStr);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }
        return date;
    }

    public static String formatDateHHMM(Date date) {
        String dateStr = "";

        try {
            dateStr = DATETIMEHHMM.format(date);
        } catch (Exception e) {
            log.error("ERROR " + e);
        }

        return dateStr;
    }

    public static String getPrintDate() {

        return printDF.format(getDate());
    }

    /**
     * Get the String value of Timestamp before current date, the String format is dd/MM/yyyy HH:mm
     *
     * @return
     */
    public static String formatLastDayHHMM() {
        Date yestoday = add(new Date(), Calendar.DATE, -1);
        return formatDateHHMM(yestoday);
    }

    /**
     * Validate the dateTime format dd/MM/yyyy HH:mm
     *
     * @param value
     * @return
     */
    public static boolean isValidDateTimeHHMM(String value) {
        try {
            if (StringUtil.isEmpty(value) || value.length() != 16) {
                return false;
            }
            return (isValidDate(value.substring(0, 10)) && isValidTime(Integer.valueOf(value.substring(11, 13)), Integer.valueOf(value.substring(15))));
        } catch (NumberFormatException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Validate the time format HH:mm:ss
     *
     * @param value
     * @return
     */
    public static boolean isValidTime(String value) {
        if (value == null || value.length() != 8) {
            return false;
        }
        try {
            int hour = Integer.valueOf(value.substring(0, 2));
            int minute = Integer.valueOf(value.substring(3, 5));
            int second = Integer.valueOf(value.substring(6));
            return (hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        return false;
    }

    public static Date getStartOfDay(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        // Clear the time component
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DAY_OF_MONTH);

        return getDate(year, month, day);
    }

    public static Date getEndOfDay(Date date) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);

        // Clear the time component
        cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY));
        cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE));
        cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND));
        cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND));

        // System.out.println( "cal.toString() = " + cal.toString() );

        return cal.getTime();
    }

    public static Date getDate(int year, int month, int day) {
        Calendar cal = Calendar.getInstance();

        // Clear all fields first
        cal.clear();

        cal.set(year, month - 1, day);

        return cal.getTime();
    }

    public static Date getStartDayOfMonth(Date aDate) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(aDate);
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMinimum(Calendar.DAY_OF_MONTH));
        return DateUtil.getStartOfDay(cal.getTime());
    }

    public static Date getEndDayOfMonth(Date aDate) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(aDate);
        cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
        return DateUtil.getEndOfDay(cal.getTime());
    }

    public static Date getStartDayOfWeek(Date aDate) {
        Calendar cal = Calendar.getInstance();
        cal.setTime(aDate);
        cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
        return cal.getTime();
    }

    public static Date getEndDayOfWeek(Date aDate) {
        return DateUtil.add(getStartDayOfWeek(aDate), Calendar.DATE, 6);
    }

    public static Date getStartDayOfWeek(Date aDate, int offset) {
        return DateUtil.add(getStartDayOfWeek(aDate), Calendar.WEEK_OF_YEAR, offset);
    }

    public static Date getEndDayOfWeek(Date aDate, int offset) {
        return DateUtil.add(getEndDayOfWeek(aDate), Calendar.WEEK_OF_YEAR, offset);
    }

    public static Date getStartDayOfMonth(Date aDate, int field, int offset) {
        return getStartDayOfMonth(DateUtil.add(aDate, field, offset));
    }

    public static Date getEndDayOfMonth(Date aDate, int field, int offset) {
        return getEndDayOfMonth(DateUtil.add(aDate, field, offset));
    }

    public static int getWorkingDateRange(Date sDate,Date eDate){
        Date startDate=sDate;
        int count=0;
        while(startDate.compareTo(eDate)<=0){
           if(isWorkingDay(startDate)){
               count++;
           }
           startDate=DateUtil.add(startDate,Calendar.DATE,1);
        }
        return count;
    }

    public static boolean isWorkingDay(Date date){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date); 
        int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)-1;
        return dayOfWeek!=0&&dayOfWeek!=6;
    }

    public static void main(String[] args) {
//        System.out.println(getDate());
//        System.out.println(getCurrentTimestamp());
//        System.out.println(formatDateTime(getDate()));
//        System.out.println(formatDateMillis(getDate()));
//        System.out.println(formatDate(getDate()));
//        System.out.println(parse("25/05/2009"));
//        System.out.println(parseTimestamp("25/05/2009"));
//        System.out.println(parseDateMillis("25/05/2009 14:14:14.123"));
//        System.out.println("formatLastDayHHMM : " + formatLastDayHHMM());
//        System.out.println(isValidDate(2003, 2, 29));
//        System.out.println(isValidDate("29/02/2009"));
//        System.out.println(formatDateMicros(getCurrentTimestamp()));
//        System.out.println(parseDateTimeHHMM("25/02/2009 14:14"));
//        System.out.println(formatDateHHMM(parseDateTimeHHMM("25/02/2009 14:14")));
//        System.out.println(getFullDate(parse("25/05/2009"), parseDateTimeHHMM("25/02/2009 14:14")));
//        System.out.println(getMonthStartDate(new Date()));
        // System.out.println(getEndOfDay(new Date()));
       /* System.out.println(getStartDayOfWeek(new Date()));
        System.out.println(getStartDayOfWeek(new Date(), 1));
        System.out.println(getStartDayOfWeek(new Date(), -1));
        System.out.println(getStartDayOfMonth(new Date(),Calendar.MONTH, 1));
        System.out.println(getEndDayOfMonth(new Date(),Calendar.MONTH, -1));        
        System.out.println(getEndDayOfWeek(new Date()));
        System.out.println(getStartDayOfMonth(new Date()));
       System.out.println(getEndDayOfMonth(new Date()));   */ 

    }
}
